Introduction

Every day, more than 120 people in America are killed with guns. The gun homicide rate in the U.S. is 26 times higher than that of other developed countries Everytown. In 2024, the US Surgeon General’s Advisory declared gun violence as a public health crisis US Surgeon General.

Charlottesville and Albemarle are not immune to this epidemic. On November 13, 2022, three UVA football players were shot and killed on campus. Devin Chandler, Lavel Davis Jr. and D’Sean Perry lost their lives to an act of gun violence. Additionally, the rate of violent crime in Charlottesville and Albemarle increased by 30% from 2021 to 2022, and in the early months of 2023, police had already investigated five homicide cases; compared to zero cases in 2021 CSWG Report. While incidents of gun violence have decreased in the first half of 2024Cville Weekly, the number bullets recovered at the scenes has increased, indicating the ever-changing nature of the issue.

The Gun Violence Solutions Project is a University-sponsored effort to work with community partners on assessing, developing, and implementing actionable solutions to reduce gun violence in smaller towns. The project is taking a holistic approach by researching solutions at a local level in the Charlottesville and Albemarle region, while also exploring law and policy alongside the historical, cultural, and structural factors that shape gun violence.

This document is meant to support the work of the Gun Violence Solutions Project to help us understand the nature and shape of gun violence in our community. Together we can develop a shared understanding and greater clarity of the problem, identify knowledge and data gaps, and facilitate wider community engagement in conversations about solutions. The residents of Charlottesville and Albemarle are the largest stakeholders. These folks ARE the data. They have the lived experiences, and are impacted in various ways. Their voices should be centered.

Ultimately, we hope this work will be part of:

  1. Understanding the full impact of gun violence on our community and how different people experience this impact;

  2. Working with residents, advocates, and decision makers to identify approaches to prevention and intervention for all types of gun violence, and

  3. Evaluating the effectiveness of current and future efforts to eliminate gun violence.

Data Notes

The data listed below represent information that is publicly and readily available online. This document is meant to serve as an overview of the existing resources so that we can begin representing the nature, shape, and dimensions of the problem locally.

All of this data is administrative and made up of information created when people interact with public services. As a result, we need to remember the deeply human aspect of this data both in terms of input mechanisms and the data values, themselves. Granted personal info is scrubbed from this report, we should keep in mind that these are individuals and members of our community that are being represented.

The script scripts/dataprep.R outlines the process for accessing and cleaning each data source.

Key Terminology

The data discussed comes from news outlets, the Virginia Department of Health, and local and national law enforcement agencies. To keep insights consistent across these sources we need clearly defined terminology. Each of the terms below has a specific and unique definition, and we will consistently use these terms throughout the analysis.

Please refer to the Terminology section of the Appendix for more information and a list of definitions.


Firearm Injuries & Deaths

The leading cause of death by gun violence in the Blue Ridge Health District is suicide, which makes up 76% of all firearm-related deaths from 2018 to 2022. The Blue Ridge Health District includes Charlottesville City, Albemarle, Greene, Louisa, Fluvanna, and Nelson County. The chart below includes the number and rate of firearm-related deaths among Blue Ridge Health District residents. Adversarial community violence accounts for 22% of all deaths.

Source: https://data.virginia.gov/dataset/vdh-pud-firearm-deaths-by-district-intent


The Virginia Open Data Portal includes information about emergency department visits for firearm injuries from 2015 to 2023 along with death certificates for firearm-related causes of death from 2018 to 2022. This data is supplied by the Virginia Department of Health.

Identifiers & Exclusions

Identifiers

Firearm injury visits are identified using key terms in the chief complaint (reason for visit) and discharge diagnosis codes. Key terms include: gun with wound, GSW, gunshot, buckshot, revolver, rifle, shotgun, firearm, pistol, handgun, been shot, I was shot, I got shot, combination of hit, ricochet, graze with bullet. ICD-10 diagnosis codes: W32.0, W32.1, W33.0, W33.1, W34.0, W34.1, X72, X73, X74, X93, X94, X95, Y22, Y23, Y24, Y35.0, Y38.4 SNOMED diagnosis codes: 41430008, 56768003, 63409001, 69861004, 77301004, 86122002, 111050005, 219257002, 283545005, 218081007, 218086002, 218082000, 218087006, 218088001, 269796009, 242869008, 219199009, 219200007, 219201006, 219204003, 219205002, 219203009, 219198001, 219142001, 219143006, 219144000, 219145004, 219146003, 287184008, 287193009.

Exclusions

Exclusions include follow-up visits, visits involving other types of guns (e.g., staple gun), visits where firearm was used as a weapon but not fired (e.g., pistol whip). City/county localities are assigned using the patient’s residential zip code for Virginia residents. Patients with non-Virginia or unknown zip codes are grouped as ‘Out of State.’


Injuries in Cville/Albemarle

The Virginia Department of Health groups Charlottesville City and Albemarle County together for their firearm injury counts from 2015 - 2023. This aggregation does not include any demographic information.

The table below shows that the rate of firearm injuries peaked at 8.3% in 2020, however the total number of emergency department visits was the lowest of the sample. After 2020, both the numbers of firearm injuries and ED visits are increasing, subsequently causing the rate of injury to trend upwards as well.

fai_county <- read_csv("data/fai_county.csv") 

albemarle <- fai_county %>%
  filter(str_detect(patient_city_county, "Albemarle"))
  
albemarle %>%
  arrange(year) %>%
  select(-patient_city_county) %>%
  reactable(
    defaultColDef = colDef(
      header = function(value)
        str_to_title(gsub("_", " ", value, fixed = TRUE)),
      align = "center",
      defaultSortOrder = "desc",
      headerStyle = list(background = "#f7f7f8")
    ),
    columns = list(
      total_ed_visits = colDef(format = colFormat(separators = TRUE)),
      rate_of_firearm_injuries_per_10k_ed_visits = colDef(style = color_scales(
        albemarle, colors = c("#FFFFFF", "#FA8C00")
      ))
    ),
    bordered = TRUE,
    highlight = TRUE
  ) %>%
    add_legend(albemarle, col_name = 'rate_of_firearm_injuries_per_10k_ed_visits', title = 'Rate of Firearm Injuries per 10k ED Visits', footer = 'Reported as of 2023', colors = c("#FFFFFF", "#FA8C00"))
Rate of Firearm Injuries per 10k ED Visits
  • 8.3
  • 6.4
  • 5.4
  • 4.4
  • 3.9
Reported as of 2023

Deaths in the Blue Ridge Health District

The VDH Office of Vital Records tracks causes of death among Virginia residents using death certificates. Codes on the death certificate indicate underlying and contributing causes of death. Deaths are classified as firearm-related using the definition from the Centers for Disease Control CDC.

This VDH dataset includes the number and rate of firearm-related deaths among Virginia residents by patient health district and age from 2018 through 2022. Virginia Department of Health districts are assigned based on the residence of the patient at the time of death, not where the death occurred. Data includes Virginia residents only, whether or not they died in Virginia.

The table below shows that the rate of firearm death is highest for individuals aged 18 and 19 years old and stays above 14% for individuals up to 44 years old. The age group with the second-highest percentage of gun-related deaths are individuals above the age of 75. Given that suicide is the number one cause of firearm-related death in our region, it can be inferred that a majority of the deaths for individuals 75 and older were suicide.

fai_age <- read_csv("data/fai_age.csv")

fai_age %>%
  select(-health_district) %>%
  arrange(age_group) %>%
  reactable(
    defaultColDef = colDef(
      header = function(value) str_to_title(gsub("_", " ", value, fixed = TRUE)),
      align = "center",
      defaultSortOrder = "desc",
      headerStyle = list(background = "#f7f7f8")),
    columns = list(
      rate_of_firearm_deaths_per_100k_pop = colDef(style = color_scales(fai_age, colors = c("#FFFFFF", "#FA8C00"))
        )),
    bordered = TRUE,
    highlight = TRUE,
    defaultPageSize = 11) %>%
    add_legend(fai_age, col_name = 'rate_of_firearm_deaths_per_100k_pop', title = 'Rate of Firearm Deaths per 100k Pop', footer = 'Years: 2018 - 2022', colors = c("#FFFFFF", "#FA8C00"))
Rate of Firearm Deaths per 100k Pop
  • 18.3
  • 14.8
  • 13.1
  • 7.3
  • 0.7
Years: 2018 - 2022

Incidents of Gun Violence

Sourced from the Gun Violence Archive, this data includes information collected from public news sources about 201 incidents of gun violence affecting 306 participants in the Charlottesville/Albemarle region from May 2014 - April 2024. The incidents range from shots fired reports, to unlawful gun ownership, to acts of violence such as assault, suicide, and homicide. The data does not include information about suicides.

The data is made up of two dataframes: one has information about each incident and the associated outcome variables (for example, the number of people injured and/or killed), and the other lists the participant-level information (for example, the age range and gender of the participants for both victims and suspects). The data includes the location and date of each incident along with characteristics such as numbers of injuries, homicides, or shots fired. The data does not give any identifiable information about the participants, including name, race, ethnicity, socioeconomic status, or mental health status.

Exclusions

Voluntary Participation

Because the data is sourced from public news websites, if an act of violence didn’t receive local news attention it was not included in the Gun Violence Archive database. That means this data underrepresents some information, especially shots fired numbers. Notably, it looks like shots fired incidents haven’t been added to the database since 2021.

Suicides

Suicides are not part of the Gun Violence Archive’s normal data collection procedures. Individual suicides are collected through the CDC’s Annual Report CDC and, because of privacy and CDC policy, they are only available as an aggregate number, without detail. Suicides associated with Officer Involved incidents and Murder-Suicides are included in Gun Violence Archive tallies.


Incident-level data

gva_incidents <- read_csv("data/gva_incidents.csv") 

gva_incidents %>%
  group_by(yr = year(incident_date)) %>%
  summarise(total_injured = sum(victims_injured + suspects_injured),
            total_killed = sum(victims_killed + suspects_killed)) %>%
  ungroup() %>%
  pivot_longer(matches("total")) %>% 
  ggplot(aes(x = yr, y = value, fill = name)) +
  geom_col() +
  labs(title = "Victims of Gun Violence in the Charlottesville/Albemarle Region",
       x = "Year",
       y = "Number of Victims",
       caption = "Total incidents: 201") +
  scale_fill_manual(labels = c("Injured", "Killed"),
                    values = c("#F8BE3D", "#007BAB"),
                    guide = guide_legend(title = "Victim Status"))


Individual-level data

gva_participants <- read_csv("data/gva_participants.csv") 

gva_participants %>%
  drop_na(age) %>%
  mutate(yr = year(incident_date)) %>%
  ggplot(aes(age, fill = role)) +
  geom_histogram(position = 'identity') +
  facet_grid(~role) +
scale_fill_manual(labels = c("Suspect", "Victim"),
                    values = c("#F8BE3D", "#007BAB"),
                    guide = guide_legend(title = "Participant Role")) +
  labs(title = "Ages of Participants",
       x = "Age",
       y = "Number of People", 
       caption = "Total particpants: 306") +
  theme(axis.text.x = element_text(angle=35))


Crimes Involving Firearms

Crime data for Virginia are pulled from the FBI Uniform Crime Reporting database which uses the National Incident-Based Reporting System (NIBRS). The data contains summaries of all crimes where a firearm was used in Virginia, Albemarle County, and Charlottesville City between 2016 - 2022. Each row represents a different type of crime for each year and each column represents crime counts and rates. The data represents the number of crimes and the number of incidents, along with the age group of the offender.

Exclusions

Voluntary Participation

Because the data is voluntarily submitted by individual police departments it may not be all-inclusive.


All Incidents

ucr <- read_csv("data/ucr_firearm.csv")
pops <- read_csv("data/ucr_pops.csv")

# Plotting rates of total incidents
ucr %>% 
  separate(type, c('group', 'type')) %>% 
  filter(group == "incident") %>%
  group_by(district, year, group) %>% 
  summarise_at(vars(matches('n_')), ~sum(.)) %>% 
  ungroup() %>% 
  left_join(pops) %>% 
  mutate_at(vars(matches('n_')), ~ . / est_pop_district * 1e4 ) -> tmp_plt_dat

ggplot(tmp_plt_dat, aes(year, n_total, color = district)) +
  geom_line(size = 1.5) +
  labs(x = "Year",
       y = "Crime Rate per 100k",
       title = "All Incidents Involving Firearms") +
  scale_color_manual(values = c("#232D4B", "#F8BE3D", "#007BAB"), 
                    name = "Region",
                    breaks = c("virginia", "charlottesville", "albemarle"),
                    labels= c("Virginia", "Charlottesville", "Albemarle")) +
  scale_y_continuous(labels=function(x) paste0(x,"%")) +
  geom_label(data = filter(tmp_plt_dat, year == 2022), 
             aes(label = paste0(round(n_total, 1),"%")), 
             show.legend = F, hjust = .75, alpha = 0.75, fontface = "bold")


Youth Incidents

# Plotting rates of juvenile incidents
ucr %>% 
  separate(type, c('group', 'type')) %>% 
  filter(group == "incident") %>%
  group_by(district, year, group) %>% 
  summarise_at(vars(matches('n_')), ~sum(.)) %>% 
  ungroup() %>% 
  left_join(pops) %>% 
  mutate_at(vars(matches('n_')), ~ . / est_pop_district * 1e4 ) -> tmp_plt_dat
  
ggplot(tmp_plt_dat, aes(year, n_juvenile, color = district)) +
  geom_line(size = 1.5) +
  labs(x = "Year",
       y = "Crime Rate per 100k",
       title = "All Youth Incidents Involving Firearms") +
  scale_color_manual(values = c("#232D4B", "#F8BE3D", "#007BAB"), 
                    name = "Region",
                    breaks = c("virginia", "charlottesville", "albemarle"),
                    labels= c("Virginia", "Charlottesville", "Albemarle")) +
  scale_y_continuous(labels=function(x) paste0(x,"%")) +
  geom_label(data = filter(tmp_plt_dat, year == 2022), 
             aes(label = paste0(round(n_juvenile, 1),"%")), 
             show.legend = F, hjust = .75, alpha = 0.75, fontface = "bold")


Adult Compared to Youth

age_labs <- c("Under 18", "Above 18")
names(age_labs) <- c("n_juvenile", "n_adult")


ucr %>% 
  select(district, year, n_juvenile, n_adult, type) %>%
  separate(type, c('group', 'type')) %>% 
  filter(group == "incident") %>%
  group_by(district, year, group) %>% 
  summarise_at(vars(matches('n_')), ~sum(.)) %>%
  ungroup() %>%
  left_join(pops) %>%
  mutate_at(vars(matches('n_')), ~ . / est_pop_district * 1e4 ) %>%
  pivot_longer(cols = c(n_juvenile, n_adult)) -> tmp_plt_dat

ggplot(tmp_plt_dat, aes(year, value, color = district)) +
  geom_line(linewidth = 1.5) +
  facet_wrap(~name,
             labeller = labeller(name = age_labs),
             scales = "free_y") +
  labs(x = "Year",
       y = "Crime Rate per 100k",
       title = "Adult Compared to Youth Incident Rates") +
  scale_color_manual(values = c("#232D4B", "#F8BE3D", "#007BAB"), 
                    name = "Region",
                    breaks = c("virginia", "charlottesville", "albemarle"),
                    labels= c("Virginia", "Charlottesville", "Albemarle")) +
  scale_y_continuous(labels=function(x) paste0(x,"%")) +
  geom_label(data = filter(tmp_plt_dat, year == 2022), 
             aes(label = paste0(round(value, 1),"%")), 
             show.legend = F, hjust = .75, alpha = 0.75, fontface = "bold")


Police Reporting

This dataset includes an anonymized collection of 890 gun-related incidents recorded by the Charlottesville Police Department (CPD), the Albemarle County Police Department (ACPD), and the UVA Police Department (UPD) between January 1, 2019 - May 18, 2024. The data was collected from their SQL database by the ACPD Sr. Crime Analyst, de-identified, and then shared with the Equity Center in June 2024. To preserve anonymity while also providing geographic information, approximate block numbers are used instead of specific addresses.

Each row represents the initial information that is provided by individuals calling for police assistance. Some incidents are listed as “Unverified” which means that although a call was made to the police, there was not sufficient evidence to confirm an incident of gun violence actually occurred (see Exclusions section below.)

As a result, this is not a definitive collection of public safety data for Charlottesville and Albemarle. We should consider the factors that influence the inclination of an individual calling the police. Are there systematic reasons that someone might call the police more or less than someone else? It’s also significant to note that just because a call was made, does not mean that an arrest was made. All individuals are innocent until proven guilty in a court of law.

Exclusions

Verification

Each incident is listed as being verified, unverified, or as a case. An incident is considered verified if it can be proved by an officer to have happened or there is reasonable evidence of it’s occurrence. For example, a shots fired incident would be verified if there were shell casings found at the scene.

An incident would be considered unverified if there was no proof after the fact that it happened. For example, someone may call the police to report hearing loud noises, however when the officers arrived they find either no evidence of a shooting, or evidence to prove otherwise, such as used fireworks supplies.

An incident would be marked as a case if it required significant follow up, or was otherwise recorded as a crime to be investigated. Typically more violent incidents such as aggravated assault or homicide become cases.

regional_gv <- read_csv("data/regional_gv.csv") %>%
  mutate(locality = word(locality, 1),
         yr = year(reported_date))

shots_fired <- regional_gv %>%
  filter(description == "Shots Fired",
         verified == "VERIFIED") %>%
  group_by(yr, locality) %>%
  count() %>%
  rename(shots_fired = n)

other <- regional_gv %>%
  filter(description != "Shots Fired",
         verified != "UNVERIFIED") %>%
  group_by(yr, locality) %>%
  count() %>%
  rename(other = n)

years <- shots_fired %>%
  left_join(other, relationship = "many-to-many") %>%
  pivot_longer(cols = c("shots_fired", "other"),
               names_to = c("type"),
               values_to = "count")
  
ggplot(years, aes(yr, count, fill = type)) +
  geom_col() +
  facet_wrap(~locality) +
  scale_fill_manual("Incident Type",
                    labels = c("Aggravated Assault & Homicide", "Shots Fired"),
                    values = c("#F8BE3D", "#007BAB")) +
  labs(title = "Incidents of Gun Violence in Charlottesville & Albemarle",
       x = "Year",
       y = "Number of Verified Incidents",
       caption = "Note: the 2024 data only includes incidents up to May 18.")

Charlottesville Open Data Portal

Another way of exploring local gun violence data is through the publicly available Charlottesville Open Data Portal. This database includes 526 gun-related crime reports, and 390 gun-related arrests in City of Charlottesville from March 2019 through April 2024.

This data is different from the data provided directly from the police in the following ways:

  1. It is limited to only reports in the Charlottesville district
  2. It includes all calls to police, and not just gun-related calls
  3. It does not include additional incident information such as verification or if it was assigned to an detective as a case.

This data can be considered the first step in how gun-related crimes are recorded while the police-provided data can be considered the second step in the process.

The dataset is made up of two dataframes: one includes information about initial crime reports and the other includes arrest data. Both datasets have been anonymized to remove names and other identifiable information such as house numbers.

Exclusions

Jurisdiction Limits

These datasets only contain information reported by City of Charlottesville Police. It does not contain information about incidents that solely involve other police departments operating within the city (for example, University of Virginia police or Albemarle County police).

Filtering

The data has been filtered to only include crime reports that involve firearms, which required some subjective finagling. To do this, only offences labeled Shots Fired/Illegal Hunting, Robbery - Armed, and Weapons Violations are used. As a result, this data may significantly underrepresent the number of firearm incidents. For example, crime reports that have Assault as the primary offense are not included because we cannot be certain the assault included firearm.

odp_crimes <- read_csv("data/odp_crimes.csv") 
odp_arrests <- read_csv("data/odp_arrests.csv")

cville_map <- get_map(c(left = -78.53, bottom = 38.00, right = -78.45, top = 38.07), 
                      maptype = "roadmap", color = "bw")

# Geographic spread
ggmap(cville_map) +
  stat_density2d(data = odp_crimes, aes(fill = ..level.., alpha = 0.1), # removes 55 values
                 geom = "polygon") +
  theme(legend.position="none") +
  scale_fill_viridis_c(direction = -1) +
  theme_void() +
  theme(legend.position = "none") +
  labs(title = "Gun-Related Crime Reports",
       caption = "Total reports: 526")

ggmap(cville_map) +
  stat_density2d(data = odp_arrests, aes(fill = ..level.., alpha = 0.1), # removes 96 values
                 geom = "polygon") +
  theme(legend.position="none") +
  scale_fill_viridis_c(direction = -1) +
  theme_void() +
  theme(legend.position = "none") +
  labs(title = "Gun-Related Arrests",
       caption = "Total arrests: 390")


Arrest Reports

From the Charlottesville Open Data Portal:

“Arrest data contains information on people taken into custody by City of Charlottesville police officers. More serious crimes such as felony offenses are more likely to result in an arrest. However, arrests can occur as a result of other offenses, such as parole violations or a failure to appear for trial.”

Given that definition, we can ask the question: are a lot of people committing a few crimes or are a few people committing a lot of crimes? The below data contains the distribution of 390 arrests for firearm-related charges between May 2019 and April 2024. The data has been anonymized so that identifiable information has been removed.

Here we can see that of the 233 individuals arrested, 147 have only one gun-related charge, 53 have two charges, and one person has 15 charges. In other words, 86% of people arrested for gun-related crimes have 2 or fewer charges.

# Arrest numbers per person 
odp_arrests %>%
  group_by(id) %>%
  summarise(charges = n()) %>%
  ungroup() %>%
  ggplot(aes(y = charges)) +
  geom_bar(fill = "#007BAB") +
  geom_text(aes(label =..count..), stat = "count", hjust = -0.25, size = 3) +
  labs(title = "Number of Firearm-Related Arrests per Person",
       x = "Number of People",
       y = "Number of Charges per Person",
       caption = "Total number of people arrested: 233") +
  scale_y_continuous(breaks = c(1:15)) +
  geom_label(aes(90, 10, label = "86% of people arrested \nfor gun-related crimes \nhave 2 or fewer charges"))


Access to Firearms

With nearly a third of Americans owning a gun PEW, access to firearms is easy for many people. Someone can legally purchase a gun anywhere from their local big-box store, to their neighborhood gun dealer, to a specialty weapons trade show. The minimum age in Virginia to purchase a handgun is 21, however individuals as young as 18 can legally purchase rifles and shotguns VSP. Residents can open carry their weapons, except in areas where firearms are prohibited by statute (such as UVA Grounds 1(https://uvapolicy.virginia.edu/policy/sec-030)), and there are no state-mandated age limits on purchasing ammunition VSP.

People can also get guns illegally. This can range from manufacturing their own, to under-the-table sales, to trade and barters, and even theft.

This section explores two ways get access to guns in Charlottesville and Albemarle: the many licensed dealers, and from thefts, specifically from vehicles.

alb_sf <- get_acs(
  geography = "county",
  state = "51",
  variables = "B01001_001",
  survey = "acs5",
  geometry = TRUE,
  year = 2022) %>%
  filter(NAME == "Albemarle County, Virginia")

Federally Licensed Firearms Dealers

The The Bureau of Alcohol, Tobacco, Firearms, and Explosives (ATF) maintains a record of all federally licensed firearms dealers. Some of these dealers are publicly accessible businesses, such as the Walmart on 29, while others are individuals that operate out of residential neighborhoods. The list and map below show the 22 authorized dealers in Charlottesville and Albemarle county as of April 2024.

Map

atf_dealers <- read_csv("data/atf_dealers.csv")

alb_map <- get_map(c(left = -79, bottom = 37, right = -78, top = 39),
                   maptype = "roadmap", color = "bw")

ggmap(alb_map) +
  geom_sf(data = alb_sf, inherit.aes = FALSE, alpha = 0, linewidth = 0.5) +
  theme_void() +
  geom_point(atf_dealers,
             mapping = aes(x = lon, y = lat, col = business_type, shape = business_type),
             size = 3) +
  scale_color_manual(name = "Business Type", values = c("#F8BE3D", "#007BAB")) +
  scale_shape_manual(name = "Business Type", values = c(17, 19)) +
  theme_void() +
  labs(title = "Locations of Firearms Dealers in Charlottesville & Albemarle",
       caption = "Total licensed dealers: 22")

Table

The type of license issued indicates if the seller is a dealer or manufacturer of firearms and/or other destructive devices like explosives. The sellers in Charlottesville and Albemarle have the following license types:

  • Type 01 - Dealer in Firearms Other Than Destructive Devices

  • Type 02 - Pawnbroker in Firearms Other Than Destructive Devices

  • Type 07 - Manufacturer of Firearms Other Than Destructive Devices

  • Type 09 - Dealer in Destructive Devices

  • Type 10 - Manufacturer of Destructive Devices, Ammunition for Destructive Devices or Armor Piercing Ammunition

atf_dealers %>%
  select(license_name, business_name, premise_street, license_type) %>%
  reactable(
    defaultColDef = colDef(
      header = function(value)
        str_to_title(gsub("_", " ", value, fixed = TRUE)),
      align = "left",
      headerStyle = list(background = "#f7f7f8")
    ),
    columns = list(
      license_type = colDef(minWidth = 50)
    ),
    bordered = TRUE,
    highlight = TRUE
  ) %>%
  reactablefmtr::add_source("List of all Federal Firearm License types: https://www.atf.gov/resource-center/fact-sheet/fact-sheet-federal-firearms-and-explosives-licenses-types", align = "right", font_size = 10, font_color = "grey")

List of all Federal Firearm License types: https://www.atf.gov/resource-center/fact-sheet/fact-sheet-federal-firearms-and-explosives-licenses-types

Theft from Vehicles

Gun thefts from cars are increasing triplefold compared to the last decade (Everytown). In 2022 in Virginia, $3,477,207 dollars worth of guns (avg. $627 dollars) and $97,797 dollars worth of firearm accessories were reported stolen, and in Charlottesville, $30,217 worth of firearms were reported stolen in 2022, and only $2,495 were reported recovered (VSP).

The plot below shows the number of guns stolen from vehicles in Charlottesville City and Albemarle County from 2016 to 2022.

theft <- read_csv("data/nibrs_theft.csv")

ggplot(theft, aes(year, n_stolen, colour = region)) +
  geom_line(linewidth = 1.5) +
  labs(x = "Year",
       y = "Number of Firearms Stolen",
       title = "Theft of Firearms from Vehicles") +
  scale_color_manual(values = c("#007BAB", "#F8BE3D"), 
                    name = "Region") +
  ggrepel::geom_label_repel(data = filter(theft, year == 2022), 
             aes(label = n_stolen), 
             show.legend = F, 
             alpha = 0.75,
             fontface = "bold")


Residental Characteristics

The above data primarily shows outcome-related information. To fully strategize on a solution, we need to try to understand the underlying causes of gun trauma in our community. This includes looking at Census data to better understand community factors such as poverty, access to resources, and other social structures that are in play.

The below maps overlay information from the 2022 American Community Survey on top of incidents of gun violence to visualize how these variables may compare.

Poverty & Childhood Poverty Rates

The Census Bureau uses a set of money income thresholds that vary by family size and composition to determine who is in poverty. If a family’s total income is less than the family’s threshold, then that family and every individual in it is considered in poverty.

Exclusions

ACS 5 Year Summaries

The American Community Survey (ACS) 5-Year Estimates are summaries for geographic areas that are based on data collected over a five-year period. The ACS data represented here is from 2018-2022. The 5-year estimates are considered more accurate than the 1-year estimates because they use a larger sample size, which results in smaller margins of error.

Because the data represents summaries from 2018-2022, the plots below do not show changes in poverty rates by individual year but are rather an average of the past 5 years. This information can still be used to estimate approximate poverty rates for 2023 and 2024, which haven’t been released in the ACS 5-Year Estimates yet.

Student Housing

A significant portion of the JPA-Fontaine Census tract is made up of UVA students. Many of these students are not earning an income and therefore skew overall poverty levels for that area.

dat <- readRDS("data/census.RDS")
gv <- read_csv("data/regional_gv.csv")

gv_2021 <- gv %>%
  filter(between(reported_date, as.Date("2021-01-01"), as.Date("2021-12-31")))
gv_2022 <- gv %>%
  filter(between(reported_date, as.Date("2022-01-01"), as.Date("2022-12-31")))
gv_2023 <- gv %>%
  filter(between(reported_date, as.Date("2023-01-01"), as.Date("2023-12-31")))
gv_2024 <- gv %>%
  filter(between(reported_date, as.Date("2024-01-01"), as.Date("2024-12-31")))

pal_pov <- colorNumeric(palette = "viridis",
                        domain = NULL,
                        reverse = TRUE)

pal_earn <- colorNumeric(palette = "viridis",
                        domain = c(15000:130000),
                        reverse = FALSE)

2021

Total incidents: 167

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Overall Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(poverty_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Childhood Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Child Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(cpov_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Child Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ poverty_est, 
            title = "Estimated Poverty Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("Overall Poverty", "Child Poverty"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2021, 
             lng = gv_2021$lon,
             lat = gv_2021$lat,
             popup = paste0("Description: ", gv_2021$description, "<br>",
                            "Date: ", gv_2021$reported_date, "<br>",
                            "Status: ", gv_2021$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2022

Total incidents: 188

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Overall Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(poverty_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Childhood Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Child Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(cpov_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Child Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ poverty_est, 
            title = "Estimated Poverty Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("Overall Poverty", "Child Poverty"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2022, 
             lng = gv_2022$lon,
             lat = gv_2022$lat,
             popup = paste0("Description: ", gv_2022$description, "<br>",
                            "Date: ", gv_2022$reported_date, "<br>",
                            "Status: ", gv_2022$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2023

Total incidents: 179

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Overall Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(poverty_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Childhood Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Child Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(cpov_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Child Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ poverty_est, 
            title = "Estimated Poverty Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("Overall Poverty", "Child Poverty"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2023, 
             lng = gv_2023$lon,
             lat = gv_2023$lat,
             popup = paste0("Description: ", gv_2023$description, "<br>",
                            "Date: ", gv_2023$reported_date, "<br>",
                            "Status: ", gv_2023$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2024

Total incidents: 70

Note: 2024 data consists of incidents from Jan 1 2024 through May 18 2024.

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Overall Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(poverty_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Childhood Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Child Poverty",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(cpov_est),
              fillOpacity = 0.5,
              popup = paste0("Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Child Poverty Rate: ", dat$cpov_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ poverty_est, 
            title = "Estimated Poverty Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("Overall Poverty", "Child Poverty"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2024, 
             lng = gv_2024$lon,
             lat = gv_2024$lat,
             popup = paste0("Description: ", gv_2024$description, "<br>",
                            "Date: ", gv_2024$reported_date, "<br>",
                            "Status: ", gv_2024$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

Median Earnings by Education Level

We chose to look at median earnings, rather than median household income for this analysis. Earnings consist of wages and salary from a job, and are usually a big source of direct income. Other sources of indirect income include Social Security payments, pensions, child support, public assistance, annuities, money derived from rental properties, interest and dividends. As a result, due to our nation’s history of generational wealth, and our inability to fully measure asset wealth, if we look only at median income we will have outliers that inaccurately skew the results.

Ultimately, the issue is not that people are unwilling to work, but that jobs are not paying enough to support a successful quality of life regardless of educational attainment.

Exclusions

ACS 5 Year Summaries

Similar to how poverty variables are calculated, the The American Community Survey (ACS) 5-Year Estimates for median earnings are summaries for geographic areas that are based on data collected over a five-year period. The ACS data represented here is from 2018-2022.

Because the data represents summaries from 2018-2022, the plots below do not show changes in earnings by individual year but are rather an average of the past 5 years. This information can still be used to estimate approximate earnings for 2023 and 2024, which haven’t been released in the ACS 5-Year Estimates yet.

Missing Data

There are a few tracts where median earnings by specific education level are not available. This generally happens when there are large margins of error in the measurements or to small sample sizes for the tract. As a result the map displays these areas in grey.

Future analysis may impute this information based on surrounding tract data, or use median household income as a placeholder.


2021

Total incidents: 167

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "All Education Levels",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_25),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_25), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "High School Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_hs),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_hs), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Bachelors Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_bd),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_bd), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_earn,
            values = c(15000:130000), 
            title = "Median Earnings",
            labFormat = labelFormat(prefix = "$"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("All Education Levels",
                                  "High School Degree or Equivalent",
                                  "Bachelors Degree or Equivalent"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2021, 
             lng = gv_2021$lon,
             lat = gv_2021$lat,
             popup = paste0("Description: ", gv_2021$description, "<br>",
                            "Date: ", gv_2021$reported_date, "<br>",
                            "Status: ", gv_2021$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2022

Total incidents: 188

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "All Education Levels",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_25),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_25), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "High School Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_hs),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_hs), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Bachelors Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_bd),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_bd), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_earn,
            values = c(15000:130000), 
            title = "Median Earnings",
            labFormat = labelFormat(prefix = "$"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("All Education Levels",
                                  "High School Degree or Equivalent",
                                  "Bachelors Degree or Equivalent"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2022, 
             lng = gv_2022$lon,
             lat = gv_2022$lat,
             popup = paste0("Description: ", gv_2022$description, "<br>",
                            "Date: ", gv_2022$reported_date, "<br>",
                            "Status: ", gv_2022$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2023

Total incidents: 179

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "All Education Levels",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_25),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_25), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "High School Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_hs),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_hs), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Bachelors Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_bd),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_bd), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_earn,
            values = c(15000:130000), 
            title = "Median Earnings",
            labFormat = labelFormat(prefix = "$"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("All Education Levels",
                                  "High School Degree or Equivalent",
                                  "Bachelors Degree or Equivalent"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2023, 
             lng = gv_2023$lon,
             lat = gv_2023$lat,
             popup = paste0("Description: ", gv_2023$description, "<br>",
                            "Date: ", gv_2023$reported_date, "<br>",
                            "Status: ", gv_2022$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2024

Total incidents: 70

Note: 2024 data consists of incidents from Jan 1 2024 through May 18 2024.

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "All Education Levels",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_25),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_25), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "High School Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_hs),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_hs), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addPolygons(group = "Bachelors Degree or Equivalent",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_earn(med_earn_bd),
              fillOpacity = 0.5,
              popup = paste0("Median Earnings: ", scales::dollar(dat$med_earn_bd), "<br>",
                             "Poverty Rate: ", dat$poverty_est, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_earn,
            values = c(15000:130000), 
            title = "Median Earnings",
            labFormat = labelFormat(prefix = "$"), 
            opacity = 1) %>%
  addLayersControl(baseGroups = c("All Education Levels",
                                  "High School Degree or Equivalent",
                                  "Bachelors Degree or Equivalent"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addMarkers(data = gv_2024, 
             lng = gv_2024$lon,
             lat = gv_2024$lat,
             popup = paste0("Description: ", gv_2024$description, "<br>",
                            "Date: ", gv_2024$reported_date, "<br>",
                            "Status: ", gv_2024$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

Unemployment Rates

Similar to how poverty variables are calculated, the The American Community Survey (ACS) 5-Year Estimates for unemployment rates are summaries for geographic areas that are based on data collected over a five-year period. The ACS data represented here is from 2018-2022.

Because the data represents summaries from 2018-2022, the plots below do not show changes in unemployment rates by individual year but are rather an average of the past 5 years. This information can still be used to estimate approximate poverty rates for 2023 and 2024, which haven’t been released in the ACS 5-Year Estimates yet.

2021

Total incidents: 167

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Unemployment Rate",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(unemployment_rate),
              fillOpacity = 0.5,
              popup = paste0("Unemployment Rate: ", dat$unemployment_rate, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ unemployment_rate, 
            title = "Unemployment Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addMarkers(data = gv_2021, 
             lng = gv_2021$lon,
             lat = gv_2021$lat,
             popup = paste0("Description: ", gv_2021$description, "<br>",
                            "Date: ", gv_2021$reported_date, "<br>",
                            "Status: ", gv_2021$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2022

Total incidents: 188

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Unemployment Rate",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(unemployment_rate),
              fillOpacity = 0.5,
              popup = paste0("Unemployment Rate: ", dat$unemployment_rate, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ unemployment_rate, 
            title = "Unemployment Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addMarkers(data = gv_2022, 
             lng = gv_2022$lon,
             lat = gv_2022$lat,
             popup = paste0("Description: ", gv_2022$description, "<br>",
                            "Date: ", gv_2022$reported_date, "<br>",
                            "Status: ", gv_2022$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2023

Total incidents: 179

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Unemployment Rate",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(unemployment_rate),
              fillOpacity = 0.5,
              popup = paste0("Unemployment Rate: ", dat$unemployment_rate, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ unemployment_rate, 
            title = "Unemployment Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addMarkers(data = gv_2023, 
             lng = gv_2023$lon,
             lat = gv_2023$lat,
             popup = paste0("Description: ", gv_2023$description, "<br>",
                            "Date: ", gv_2023$reported_date, "<br>",
                            "Status: ", gv_2023$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

2024

Total incidents: 70

Note: 2024 data consists of incidents from Jan 1 2024 through May 18 2024.

dat %>%
  st_transform(crs = 4326) %>%
  leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons(group = "Unemployment Rate",
              stroke = TRUE, 
              weight = 0.5,
              opacity = 1,
              color = "black", 
              fillColor = ~ pal_pov(unemployment_rate),
              fillOpacity = 0.5,
              popup = paste0("Unemployment Rate: ", dat$unemployment_rate, "%", "<br>",
                             "Population: ", dat$pop_est, "<br>",
                             "Tract: ", dat$tract_name, ", ", dat$locality),
              highlightOptions = highlightOptions(
                fillOpacity = 1,
                bringToFront = FALSE
              )) %>%
  addLegend("bottomright",
            pal = pal_pov,
            values = ~ unemployment_rate, 
            title = "Unemployment Rates",
            labFormat = labelFormat(suffix = "%"), 
            opacity = 1) %>%
  addMarkers(data = gv_2024, 
             lng = gv_2024$lon,
             lat = gv_2024$lat,
             popup = paste0("Description: ", gv_2024$description, "<br>",
                            "Date: ", gv_2024$reported_date, "<br>",
                            "Status: ", gv_2024$verified),
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               iconCreateFunction=JS("function (cluster) {    
    var childCount = cluster.getChildCount();  
    if (childCount < 100) {  
      c = 'rgba(211,211,211);'
    } else if (childCount < 1000) {  
      c = 'rgba(211,211,211);'  
    } else { 
      c = 'rgb(211,211,211);'  
    }    
    return new L.DivIcon({ html: '<div style=\"background-color:'+c+'\"><span>' + childCount + '</span></div>', className: 'marker-cluster', iconSize: new L.Point(40, 40) });

  }")
             ))

Next Steps

The above data primarily shows outcome-related information. To fully strategize on a solution, we need to try to understand the underlying causes of gun trauma in our community. This includes looking at additional data to better understand community factors such as regional history, adverse childhood experiences, access to resources, and other social structures that are in play.

Appendix

Terminology

Firearm Injury

The CDC defines a firearm injury as:

“A gunshot wound or penetrating injury from a weapon that uses a powder charge to fire a projectile. Weapons that use a powder charge include handguns, rifles, and shotguns. Injuries from air- and gas-powered guns, BB guns, and pellet guns are not considered firearm injuries as these types of guns do not use a powder charge to fire a projectile.”

Types of firearm injuries include:

  • Intentionally self-inflicted
  • Unintentional (accidental injuries that happen while someone is cleaning or playing with a firearm)
  • Interpersonal violence (homicide or assault)
  • Legal intervention (violence inflicted by law enforcement on duty)
  • Undetermined intent (not enough information to determine)

Gun Violence

Gun violence is an offense committed with firearms, such as handguns, shotguns, or semi-automatic rifles. It can include homicide, violent crime, attempted suicide, suicide, and unintentional death and injury. The national Gun Violence Archive describes gun violence as:

“all incidents of death or injury or threat with firearms… a shooting of a victim by a subject/suspect is considered gun violence as is a defensive use or an officer involved shooting. The act itself, no matter the reason is violent in nature.”

Gun Violence Trauma

The American Sociological Association describes gun violence trauma as the psychological and emotional impact that gun violence can have on individuals and communities. It can result from direct or indirect exposure to gun violence and has long-term negative effects on wellbeing. This information is difficult to objectively measure, so while this analysis focuses on numbers of direct exposure to gun violence for individuals it is important to remember the lasting indirect trauma gun violence has on our community.

Incident

An incident of gun violence can be defined as a time-boxed event where individual acts of violence took place. An incident may include multiple offenses. For example, if an individual went on a shooting spree and injured three people, that would be considered as one incident with three offenses.

Crime

The FBI defines gun-related crimes as the number of illegal offenses using a firearm committed during an incident of gun violence.

Gun violence is not synonymous with gun crimes. Not all crimes are violent in nature. For example, a person may be charged with the crime of illegally possessing a firearm, despite not brandishing or using the weapon.

Arrest

An arrest can be defined as the physical taking or seizing of a person by a police officer. It is an act that indicates the intention to take that person into custody. An arrest can happen after an incident of gun violence, or it can happen for nonviolent issues such as parole violations or a failure to appear for trial. Anyone arrested is considered innocent until proven guilty in a court of law.

Participant

A participant is defined as an individual involved in an act of gun violence. Participants can be victims, those injured or killed by firearms, or offenders, those using firearms. In cases of suicide, participants are considered victims. It is important to note that while the definition is limited to individuals that are physically affected by gun violence, the spectrum of victimization often extends much further to the community.

Youth

For this analysis, youth are considered to be juveniles under the age of 18.

References

Centers for Disease Control - About Firearm Injury and Death: Fast Facts - https://www.cdc.gov/firearm-violence/about/?CDC_AAref_Val=https://www.cdc.gov/violenceprevention/firearms/fastfact.html. Accessed March 15, 2024.

CDC’s Annual Report 2(https://www.cdc.gov/media/releases/2023/s0810-US-Suicide-Deaths-2022.html)

Everytown - Beyond Measure: Gun Violence Trauma - https://everytownresearch.org/report/gun-violence-trauma/. Published May 17, 2023.

US Surgeon General’s Office - The U.S. Surgeon General’s Advisory on Firearm Violence: A Public Health Crisis in America - 35https://www.hhs.gov/sites/default/files/firearm-violence-advisory.pdf. Published July 2, 2024.

UVA President’s Council - Community Safety Working Group Report - https://prescouncil.president.virginia.edu/sites/g/files/jsddwu616/files/2024-01/Final%20Community%20Safety%20Working%20Group%20Report_24.pdf. Published September 22, 2023

UVA Office of the Executive Vice President and Provost - Gun Violence Solutions Project: About - https://provost.virginia.edu/subsite/gun-violence-solutions-project/about-gvsp - Accessed June 1, 2024.

Cville Weekly - CPD Chief says crime rates lower overall despite recent spike in gun violence - https://www.c-ville.com/cpd-chief-says-crime-rates-lower-overall-despite-recent-spike-in-gun-violence - Published July 3, 2024.

PEW Research - Key facts about Americans and guns - https://www.pewresearch.org/short-reads/2023/09/13/key-facts-about-americans-and-guns/ - Published September 13, 2023.


  1. UVA Policy↩︎

  2. CDC↩︎